package com.xlizen;

import net.wimpi.modbus.Modbus;
import net.wimpi.modbus.ModbusIOException;
import net.wimpi.modbus.io.*;
import net.wimpi.modbus.msg.ModbusMessage;
import net.wimpi.modbus.msg.ModbusRequest;
import net.wimpi.modbus.msg.ModbusResponse;
import net.wimpi.modbus.util.ModbusUtil;

import java.io.DataInputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.TimeoutException;

import static net.wimpi.modbus.io.ModbusASCIITransport.FRAME_END;
import static net.wimpi.modbus.io.ModbusASCIITransport.FRAME_START;

public class ModbusBleTransport implements ModbusTransport {

    private byte[] m_InBuffer;
    private BytesInputStream m_ByteIn;         //to read message from
    private BytesOutputStream m_ByteInOut;     //to buffer message to
    private BytesOutputStream m_ByteOut;      //write frames

    private DataInputStream m_InputStream;     //used to read from
    private ASCIIOutputStream m_OutputStream;   //used to write to

    private final Object mWriteMutex = new Object();
    private final Object mReadMutex = new Object();

    private volatile Boolean isReceived = false;
    private volatile Boolean mWriteResult = false;

    private Integer timeoutRead = 3000;


    public Integer getTimeoutRead() {
        return timeoutRead;
    }

    public void setTimeoutRead(Integer timeoutRead) {
        this.timeoutRead = timeoutRead;
    }

    public ModbusBleTransport() {
        m_ByteOut = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH);
        m_InBuffer = new byte[Modbus.MAX_MESSAGE_LENGTH];
        m_ByteIn = new BytesInputStream(m_InBuffer);
        m_ByteInOut = new BytesOutputStream(m_InBuffer);
    }

    @Override
    public void close() throws IOException {
        m_InputStream.close();
        m_OutputStream.close();
    }

    @Override
    public void writeMessage(ModbusMessage msg) throws ModbusIOException {
        try {
            synchronized (m_ByteOut) {
                msg.setHeadless();
                msg.writeTo(m_ByteOut);
                byte[] buf = m_ByteOut.getBuffer();
                int len = m_ByteOut.size();

                BytesOutputStream result = new BytesOutputStream(Modbus.MAX_MESSAGE_LENGTH);
                m_OutputStream = new ASCIIOutputStream(result);
                m_OutputStream.write(FRAME_START);
                m_OutputStream.write(buf, 0, len);
                if (Modbus.debug)
                    System.out.println("Writing: " + ModbusUtil.toHex(buf, 0, len));
                m_OutputStream.write(ModbusUtil.calculateLRC(buf, 0, len)); //LRC
                m_OutputStream.write(FRAME_END);
                byte[] results = result.getBuffer();
                m_ByteOut.reset();
                m_OutputStream.flush();
                result.close();

                //将数据写到 蓝牙特性中
                isReceived = false;
                //BLE写数据确认
                synchronized (mWriteMutex) {
                    //发送前置 标志位
                    mWriteResult = false;
                    //向主线程 发送消息，通知其 发送数据
                    //TODO 写入
                    onWrite(true);
                    //执行完毕后，等待结果，最多等待3秒
                    mWriteMutex.wait(3000);
                }
                //到这里对结果进行判定，如果规定时间内，执行者没有将标志位置为true，则认定写失败
                if (!mWriteResult)
                    throw new TimeoutException("write time out");
            }
        } catch (Exception ex) {
            throw new ModbusIOException("I/O failed to write" + ex);
        }
    }

    @Override
    public ModbusRequest readRequest() throws ModbusIOException {
        return null;
    }

    @Override
    public ModbusResponse readResponse()
            throws ModbusIOException {

        boolean done = false;
        ModbusResponse response = null;
        int in = -1;

        try {
            do {
                //获取数据
                synchronized (mReadMutex) {
                    if (!isReceived)
                        mReadMutex.wait(timeoutRead);
                }
                if (!isReceived)
                    throw new TimeoutException("BLE读取数据超时");
                //1. Skip to FRAME_START
                while ((in = m_InputStream.read()) != FRAME_START) {
                    if (in == -1) {
                        throw new IOException("readResponse: I/O exception - Ble timeout.");
                    }
                }
                //2. Read to FRAME_END
                synchronized (m_InBuffer) {
                    m_ByteInOut.reset();
                    while ((in = m_InputStream.read()) != FRAME_END) {
                        if (in == -1) {
                            throw new IOException("I/O exception - Ble timeout.");
                        }
                        m_ByteInOut.writeByte(in);
                    }
                    int len = m_ByteInOut.size();
                    if (Modbus.debug)
                        System.out.println("Received: " +
                                ModbusUtil.toHex(m_InBuffer, 0, len));
                    //check LRC
                    if (((int) m_InBuffer[len - 1] & 0xff) != ModbusUtil.calculateLRC(m_InBuffer, 0, len - 1)) {
                        if (Modbus.debug)
                            System.out.println("LRC is wrong: received=" +
                                    ((int) m_InBuffer[len - 1] & 0xff) +
                                    " calculated=" + ModbusUtil.calculateLRC(m_InBuffer, 0, len - 1));
                        continue;
                    }

                    m_ByteIn.reset(m_InBuffer, m_ByteInOut.size());
                    in = m_ByteIn.readUnsignedByte();
                    // JDC: To check slave unit identifier in a response we need to know
                    // the slave id in the request.  This is not tracked since slaves
                    // only respond when a master request is made and there is only one
                    // master.  We are the only master, so we can assume that this
                    // response message is from the slave responding to the last request.
//           if (in != ModbusCoupler.getReference().getUnitID()) {
//             continue;
//           }
                    in = m_ByteIn.readUnsignedByte();
                    //create request
                    response = ModbusResponse.createModbusResponse(in);
                    response.setHeadless();
                    //read message
                    m_ByteIn.reset(m_InBuffer, m_ByteInOut.size());
                    response.readFrom(m_ByteIn);
                }
                done = true;
            } while (!done);
            return response;
        } catch (Exception ex) {
            if (Modbus.debug) System.out.println(ex.getMessage());
            throw new ModbusIOException("readResponse I/O exception - failed to read.");
        }
    }//readResponse

    void onWrite(Boolean success) {
        synchronized (mWriteMutex) {
            mWriteResult = success;
            mWriteMutex.notify();
        }
    }

    void onReceive(byte[] data) {
        synchronized (mReadMutex) {
            Arrays.fill(m_InBuffer, (byte) 0);
//            m_ByteIn = new BytesInputStream(m_InBuffer);
            m_InputStream = new DataInputStream(new ASCIIInputStream(new BytesInputStream(data)));
            isReceived = true;
            mReadMutex.notify();
        }
    }
}
